﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using InseSoft.Models;
using InseSoft.Models.Entities;
using InseSoft.Services;
using InseSoft.ViewModels;
using InseSoft.ViewModels.Events;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Ninject;

namespace InseSoft.Tests.Unit.ViewModels
{
    [TestClass]
    public class EntityViewModelTest
    {
        private readonly IEventPublisher eventPublisher;
        private readonly IKernel kernel;
        private IEntityViewModel instance;

        public EntityViewModelTest()
        {
            kernel = TestHelper.SetupDIKernel();
            eventPublisher = kernel.Get<IEventPublisher>();
        }

        [TestInitialize]
        public void TestInitialize()
        {
            instance = new EntityViewModel<Empresa>();
            kernel.Inject(instance);
            instance.Initialize();
        }
        
        [TestMethod]
        public void CurrentEntity_EntitySelected_CurrentEntityPopulated()
        {
            var newItem = new Empresa{ Id = 7};

            eventPublisher.Publish(new EntitySelected(newItem));
            
            Assert.AreEqual(newItem, instance.CurrentEntity);
        }

        [TestMethod]
        public void NewCanExecute_True()
        {  
            Assert.IsTrue(instance.NewCommand.CanExecute(null));
        }

        [TestMethod]
        public void DeleteCanExecute_ItemNotSelected_False()
        {
            instance.CurrentEntity = null;

            Assert.IsFalse(instance.DeleteCommand.CanExecute(null));
        }

        [TestMethod]
        public void DeleteCanExecute_ItemSelected_True()
        {
            instance.CurrentEntity = new Empresa();

            Assert.IsTrue(instance.DeleteCommand.CanExecute(null));
        }

        [TestMethod]
        public void SaveCanExecute_NoPendingChanges_False()
        {
            instance.CurrentEntity = new Empresa();
            var contextStub = new Mock<IInseContext>();
            contextStub.Setup(x => x.HasPendingChanges()).Returns(false);
            
            instance.DbContext = contextStub.Object;
            
            Assert.IsFalse(instance.SaveCommand.CanExecute(null));
        }

        [TestMethod]
        public void SaveCanExecute_DbContextNull_False()
        {
            instance.DbContext = null;
            Assert.IsFalse(instance.SaveCommand.CanExecute(null));
        }

        [TestMethod]
        public void SaveCanExecute_PendingChangesOneInvalid_False()
        {
            instance.CurrentEntity = new Empresa();
            var contextStub = new Mock<IInseContext>();
            var entityStub = new Mock<IEntity>();
            entityStub.Setup(x => x.IsValid).Returns(false);
            contextStub.Setup(x => x.HasPendingChanges()).Returns(true);
            contextStub.Setup(x => x.AddedOrModifiedEntities<Empresa>()).Returns(new List<IEntity> { entityStub.Object });

            instance.DbContext = contextStub.Object;

            Assert.IsFalse(instance.SaveCommand.CanExecute(null));
        }

        [TestMethod]
        public void SaveCanExecute_PendingChangesAllValid_True()
        {
            instance.CurrentEntity = new Empresa();
            var contextStub = new Mock<IInseContext>();
            var entityStub = new Mock<IEntity>();
            entityStub.Setup(x => x.IsValid).Returns(true);
            contextStub.Setup(x => x.HasPendingChanges()).Returns(true);
            contextStub.Setup(x => x.AddedOrModifiedEntities<Empresa>()).Returns(new List<IEntity> { entityStub.Object });

            instance.DbContext = contextStub.Object;

            Assert.IsTrue(instance.SaveCommand.CanExecute(null));
        }

        [TestMethod]
        public void NewExecute_CurrentEntityInstantiated()
        {
            instance.NewCommand.Execute(null);

            Assert.IsNotNull(instance.CurrentEntity);
            Assert.AreEqual(0, instance.CurrentEntity.Id);
        }

        [TestMethod]
        public void NewExecute_NewItemAddedToContext()
        {
            var context = instance.DbContext;

            instance.NewCommand.Execute(null);

            Assert.AreEqual(1, context.Empresas.Count());
            Assert.AreEqual(0, instance.CurrentEntity.Id);
        }

        [TestMethod]
        public void NewExecute_CatalogEntityAddedEventTriggered()
        {
            var notified = false;
            
            eventPublisher.GetEvent<EntityAdded>().Subscribe(added => notified = true);
            instance.NewCommand.Execute(null);

            Assert.IsTrue(notified);
        }

        [TestMethod]
        public void DeleteExecute_DialogCancelled_ItemNotRemoved()
        {
            var context = kernel.Get<IInseContext>();
            var dialogInstance = kernel.Get<IDialogService>();
            var dialogMock = Mock.Get(dialogInstance);
            const string caption = "Confirmar borrado";
            const string message = "¿Está seguro de borrar este item?";
            var item = new Empresa { Id = 7, Nombre = "Puntarenas" };
            context.Empresas.Add(item);
            dialogMock.Setup(x => x.ShowMessage(message, caption, DialogButton.OkCancel, DialogImage.Question)).Returns(DialogResponse.Cancel);

            instance.DeleteCommand.Execute(null);

            Assert.AreEqual(1, context.Empresas.Count());
        }

        [TestMethod]
        public void DeleteExecute_DialogAccepted_ItemRemoved()
        {
            var context = instance.DbContext;
            var dialogInstance = instance.DialogService;
            var dialogMock = Mock.Get(dialogInstance);
            const string caption = "Confirmar borrado";
            const string message = "¿Está seguro de borrar este item?";
            var item = new Empresa { Id = 7, Nombre = "Puntarenas" };
            context.Empresas.Add(item);
            instance.CurrentEntity = item;
            dialogMock.Setup(x => x.ShowMessage(message, caption, DialogButton.OkCancel, DialogImage.Question)).Returns(DialogResponse.Ok);

            instance.DeleteCommand.Execute(null);

            Assert.AreEqual(0, context.Empresas.Count());
            Assert.IsNull(instance.CurrentEntity);
        }

        [TestMethod]
        public void DeleteExecute_DialogAccepted_CatalogEntityRemovedEventTriggered()
        {
            var dialogInstance = instance.DialogService;
            var dialogMock = Mock.Get(dialogInstance);
            var notified = false;
            const string caption = "Confirmar borrado";
            const string message = "¿Está seguro de borrar este item?";
            dialogMock.Setup(x => x.ShowMessage(message, caption, DialogButton.OkCancel, DialogImage.Question)).Returns(DialogResponse.Ok);
            eventPublisher.GetEvent<EntityRemoved>().Subscribe(added => notified = true);

            instance.DeleteCommand.Execute(null);

            Assert.IsTrue(notified);
        }
        
        [TestMethod]
        public void SaveExecute_ContextSaveCalled()
        {
            var contextMock = new Mock<IInseContext>();
            instance.DbContext = contextMock.Object;
            contextMock.Setup(x => x.Empresas).Returns(new FakeDbSet<Empresa>());

            instance.SaveCommand.Execute(null);

            contextMock.Verify(x => x.Save(), Times.Once());
        }
        
        [TestMethod]
        public void Dispose_DbContextDisposed()
        {
            var contextMock = new Mock<IInseContext>();
            kernel.Rebind<IInseContext>().ToMethod<IInseContext>(c => contextMock.Object);
            contextMock.Setup(x => x.Dispose());
            instance.DbContext = contextMock.Object;

            instance.Dispose();

            contextMock.Verify(x => x.Dispose(), Times.AtLeastOnce());
            Assert.IsNull(instance.DbContext);
        }
        
        [TestMethod, ExpectedException(typeof(NullReferenceException))]
        public void ExitExecute_Success()
        {
            instance.ExitCommand.Execute(null);
        }

        [TestMethod]
        public void ContextTest()
        {
            var context = new InseContext(); 

            var result = context.Set<Empresa>().ToList();
            
            Debug.WriteLine("Results: " + result.Count);
        }
    }
}
